/** @file
  The mian interface of IPsec Protocol.

  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "IpSecConfigImpl.h"
#include "IpSecImpl.h"

EFI_IPSEC2_PROTOCOL  mIpSecInstance = { IpSecProcess, NULL, TRUE };

/**
  Handles IPsec packet processing for inbound and outbound IP packets.

  The EFI_IPSEC_PROCESS process routine handles each inbound or outbound packet.
  The behavior is that it can perform one of the following actions:
  bypass the packet, discard the packet, or protect the packet.

  @param[in]      This             Pointer to the EFI_IPSEC2_PROTOCOL instance.
  @param[in]      NicHandle        Instance of the network interface.
  @param[in]      IpVersion        IPV4 or IPV6.
  @param[in, out] IpHead           Pointer to the IP Header.
  @param[in, out] LastHead         The protocol of the next layer to be processed by IPsec.
  @param[in, out] OptionsBuffer    Pointer to the options buffer.
  @param[in, out] OptionsLength    Length of the options buffer.
  @param[in, out] FragmentTable    Pointer to a list of fragments.
  @param[in, out] FragmentCount    Number of fragments.
  @param[in]      TrafficDirection Traffic direction.
  @param[out]     RecycleSignal    Event for recycling of resources.

  @retval EFI_SUCCESS              The packet was bypassed and all buffers remain the same.
  @retval EFI_SUCCESS              The packet was protected.
  @retval EFI_ACCESS_DENIED        The packet was discarded.

**/
EFI_STATUS
EFIAPI
IpSecProcess (
  IN     EFI_IPSEC2_PROTOCOL             *This,
  IN     EFI_HANDLE                      NicHandle,
  IN     UINT8                           IpVersion,
  IN OUT VOID                            *IpHead,
  IN OUT UINT8                           *LastHead,
  IN OUT VOID                            **OptionsBuffer,
  IN OUT UINT32                          *OptionsLength,
  IN OUT EFI_IPSEC_FRAGMENT_DATA         **FragmentTable,
  IN OUT UINT32                          *FragmentCount,
  IN     EFI_IPSEC_TRAFFIC_DIR           TrafficDirection,
     OUT EFI_EVENT                       *RecycleSignal
  )
{
  IPSEC_PRIVATE_DATA     *Private;
  IPSEC_SPD_ENTRY        *SpdEntry;
  EFI_IPSEC_SPD_SELECTOR *SpdSelector;
  IPSEC_SAD_ENTRY        *SadEntry;
  LIST_ENTRY             *SpdList;
  LIST_ENTRY             *Entry;
  EFI_IPSEC_ACTION       Action;
  EFI_STATUS             Status;
  UINT8                  *IpPayload;
  UINT8                  OldLastHead;
  BOOLEAN                IsOutbound;

  if (OptionsBuffer == NULL ||
      OptionsLength == NULL ||
      FragmentTable == NULL ||
      FragmentCount == NULL
      ) {
    return EFI_INVALID_PARAMETER;
  }
  Private         = IPSEC_PRIVATE_DATA_FROM_IPSEC (This);
  IpPayload       = (*FragmentTable)[0].FragmentBuffer;
  IsOutbound      = (BOOLEAN) ((TrafficDirection == EfiIPsecOutBound) ? TRUE : FALSE);
  OldLastHead     = *LastHead;
  *RecycleSignal  = NULL;
  SpdList         = &mConfigData[IPsecConfigDataTypeSpd];

  if (!IsOutbound) {
    //
    // For inbound traffic, process the ipsec header of the packet.
    //
    Status = IpSecProtectInboundPacket (
              IpVersion,
              IpHead,
              LastHead,
              OptionsBuffer,
              OptionsLength,
              FragmentTable,
              FragmentCount,
              &SpdSelector,
              RecycleSignal
              );

    if (Status == EFI_ACCESS_DENIED || Status == EFI_OUT_OF_RESOURCES) {
      //
      // The packet is denied to access.
      //
      goto ON_EXIT;
    }

    if (Status == EFI_SUCCESS) {

      //
      // Check the spd entry if the packet is accessible.
      //
      if (SpdSelector == NULL) {
        Status = EFI_ACCESS_DENIED;
        goto ON_EXIT;
      }

      Status =  EFI_ACCESS_DENIED;
      NET_LIST_FOR_EACH (Entry, SpdList) {
        SpdEntry = IPSEC_SPD_ENTRY_FROM_LIST (Entry);
        if (IsSubSpdSelector (
              (EFI_IPSEC_CONFIG_SELECTOR *) SpdSelector,
              (EFI_IPSEC_CONFIG_SELECTOR *) SpdEntry->Selector
              )) {
          Status = EFI_SUCCESS;
        }
      }
      goto ON_EXIT;
    }
  }

  Status  = EFI_ACCESS_DENIED;

  NET_LIST_FOR_EACH (Entry, SpdList) {
    //
    // For outbound and non-ipsec Inbound traffic: check the spd entry.
    //
    SpdEntry = IPSEC_SPD_ENTRY_FROM_LIST (Entry);

    if (EFI_ERROR (IpSecLookupSpdEntry (
                     SpdEntry,
                     IpVersion,
                     IpHead,
                     IpPayload,
                     OldLastHead,
                     IsOutbound,
                     &Action
                     ))) {
      //
      // If the related SPD not find
      //
      continue;
    }

    switch (Action) {

    case EfiIPsecActionProtect:

      if (IsOutbound) {
        //
        // For outbound traffic, lookup the sad entry.
        //
        Status = IpSecLookupSadEntry (
                   Private,
                   NicHandle,
                   IpVersion,
                   IpHead,
                   IpPayload,
                   OldLastHead,
                   SpdEntry,
                   &SadEntry
                   );

        if (SadEntry != NULL) {
          //
          // Process the packet by the found sad entry.
          //
          Status = IpSecProtectOutboundPacket (
                    IpVersion,
                    IpHead,
                    LastHead,
                    OptionsBuffer,
                    OptionsLength,
                    FragmentTable,
                    FragmentCount,
                    SadEntry,
                    RecycleSignal
                    );

        } else if (OldLastHead == IP6_ICMP && *IpPayload != ICMP_V6_ECHO_REQUEST) {
          //
          // TODO: if no need return not ready to upper layer, change here.
          //
          Status = EFI_SUCCESS;
        }
      } else if (OldLastHead == IP6_ICMP && *IpPayload != ICMP_V6_ECHO_REQUEST) {
        //
        // For inbound icmpv6 traffic except ping request, accept the packet
        // although no sad entry associated with protect spd entry.
        //
        Status = IpSecLookupSadEntry (
                   Private,
                   NicHandle,
                   IpVersion,
                   IpHead,
                   IpPayload,
                   OldLastHead,
                   SpdEntry,
                   &SadEntry
                   );
        if (SadEntry == NULL) {
          Status = EFI_SUCCESS;
        }
      }

      goto ON_EXIT;

    case EfiIPsecActionBypass:
      Status = EFI_SUCCESS;
      goto ON_EXIT;

    case EfiIPsecActionDiscard:
      goto ON_EXIT;
    }
  }

  //
  // If don't find the related SPD entry, return the EFI_ACCESS_DENIED and discard it.
  // But it the packet is NS/NA, it should be by passed even not find the related SPD entry.
  //
  if (OldLastHead == IP6_ICMP &&
      (*IpPayload == ICMP_V6_NEIGHBOR_SOLICIT || *IpPayload == ICMP_V6_NEIGHBOR_ADVERTISE)
      ){
    Status = EFI_SUCCESS;
  }

ON_EXIT:
  return Status;
}

